package simulationhandlers;

import forces.CenterOfMass;
import forces.Force;
import forces.Gravity;
import forces.Viscosity;
import forces.WallForce;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import physicalobjects.Mass;
import physicalobjects.PhysicalObject;
import physicalobjects.Spring;



/**
 * Simulates objects moving around in a bounded environment.
 * @author Robert C. Duvall
 * modified by Kathleen Lan, Seon Kang
 */
public class Simulation {
    private List<Assembly> myAssemblyList;

    private Canvas myContainer;
    private final double myStartMassTemp = 10;
    private final int myWallThree = 3;
    private final int myWallFour = 4;

    private Spring myTempInteractionSpring;
    private Mass myTempInteractionMass;

    private int myTempMassID = -1;
    private double myMassTemp = myStartMassTemp;
    private double myKTemp = 1;

    private HashMap<String, Force> myGlobalForces = new HashMap<String, Force>();
    private Force[] myForceTypes = {new Gravity(), new Viscosity(),
        new CenterOfMass(), new WallForce(1), new WallForce(2),
        new WallForce(myWallThree), new WallForce(myWallFour) };

    /**
     * Create a Canvas with the given size.
     * @param container 
     */
    public Simulation (Canvas container) {
        myAssemblyList = new ArrayList<Assembly>();
        myContainer = container;
        setupDefaultForces();
    }

    /**
     * 
     */
    public void setupDefaultForces () {
        for (Force f : myForceTypes) {
            myGlobalForces.put(f.getName(), f);
        }
    }

    /**
     * Adds an empty list to each of the Lists containing lists of masses or
     * springs for each assembly The newly added lists will store the Mass and
     * Spring objects for the new assembly.
     */
    public void addAssembly () {
        myAssemblyList.add(new Assembly());
    }

    /**
     * Clears the index-th assembly from the simulation.
     * 
     * @param index 
     */
    public void removeAssembly (int index) {
        if (index < myAssemblyList.size() && index >= 0) {
            myAssemblyList.remove(index);
        }
    }

    /**
     * Clears all assemblies from the simulation.
     */
    public void clearAllAssemblies () {
        myAssemblyList.clear();
    }

    /**
     * There's code duplication, but it can't be reduced because Mass and Spring share the same
     * interface but not a superclass. There are many other such occurrences in this class, but they
     * couldn't be helped for this reason.
     * 
     * We didn't create a superclass for mass and spring because we didn't feel as though
     * they shared behavior.
     * 
     * @param obj 
     * @param n 
     */
    public void add (PhysicalObject obj, int n) {
        if (obj instanceof Mass) {
            Mass objCasted = (Mass) obj;
            myAssemblyList.get(n).add(objCasted);
        }
        else if (obj instanceof Spring) {
            Spring objCasted = (Spring) obj;
            myAssemblyList.get(n).add(objCasted);
        }
    }
    /**
     * @param mass 
     * @param n 
     */

    public void add (Mass mass, int n) {
        (myAssemblyList.get(n)).add(mass);
    }

    /**
     * 
     * @param spring 
     * @param n 
     */
    public void add (Spring spring, int n) {
        (myAssemblyList.get(n)).add(spring);
    }

    /**
     * Paint all shapes on the canvas.
     * 
     * @param pen
     *        used to paint shape on the screen
     */
    public void paint (Graphics2D pen) {
        for (Assembly a : myAssemblyList) {
            a.paint(pen);
        }
        if (myTempInteractionSpring != null) {
            myTempInteractionSpring.paint(pen);
        }
        if (myTempInteractionMass != null) {
            myTempInteractionMass.paint(pen);
        }
    }

    /**
     * Called by each step of timer, multiple times per second.
     * 
     * This should update the state of the animated shapes by just a little so
     * they appear to move over time.
     * 
     * @param dt 
     */
    public void update (double dt) {
        updateMovers(dt);
    }

    void updateMovers (double dt) {
        if (myTempInteractionSpring != null) {
            myTempInteractionSpring.update(this, dt);
        }
        for (Assembly a : myAssemblyList) {
            a.updateSprings(this, dt);
        }
        for (Assembly a : myAssemblyList) {
            a.updateMasses(this, dt);
        }
    }

    /**
     * Part of implementation of "Change size of walled area" feature.
     * @param n 
     */
    public void updateBoundSizeChange (int n) {
        for (Assembly a : myAssemblyList) {
            a.updateBoundSize(n);
        }
    }
    /**
     *
     * @param f 
     */
    public void modifyForce (Force f) {
        myGlobalForces.put(f.getName(), f);
    }

    /**
     * adds global forces with default or specified parameters to each assembly
     */
    public void addGlobalForces () {
        for (Assembly a : myAssemblyList) {
            a.clearForces();
            for (String s : myGlobalForces.keySet()) {
                a.add(myGlobalForces.get(s));
            }
        }
    }

    /**
     * Returns size (in pixels) of the game area.
     */
    public Dimension getSize () {
        return myContainer.getSize();
    }

    /**
     * 
     * @param id 
     * @param n 
     * @return
     */
    public Mass getMass (int id, int n) {
        return (myAssemblyList.get(n)).getMass(id);
    }

    /**
     * Returns the number of assemblies in the simulation.
     * 
     * @return
     */
    public int getNumberOfAssemblies () {
        return myAssemblyList.size();
    }


    /**
     * Methods below for implementing Mouse Dragging features.
     * @param p 
     */
    public void findClosestMass (Point p) {
        double shortestDistance = Math.sqrt((getSize()).getHeight() *
              (getSize()).getHeight() + (getSize()).getWidth() *
                (getSize()).getWidth());
        double assemblyShortestDistance;
        Assembly closestAssembly = myAssemblyList.get(0);
        // calculate which assembly contains the closest mass, assembly stores
        // its closest mass as an instance variable
        for (Assembly a : myAssemblyList) {
            assemblyShortestDistance = a.findClosestMass(p);
            if (assemblyShortestDistance < shortestDistance) {
                closestAssembly = a;
                shortestDistance = assemblyShortestDistance;
            }
        }
        // after finding the closest mass, instantiates a new mass at the mouse
        // position and a spring connecting the two masses
        Mass closestMass = closestAssembly.getClosestMass();
        myTempInteractionMass = new Mass(myTempMassID, p.getX(), p.getY(), myMassTemp);
        myTempInteractionSpring = new Spring(closestMass, myTempInteractionMass,
                p.distance(closestMass.getCenter()), myKTemp);
    }

    /**
     *
     */
    public void clearTempInteractionParts () {
        myTempInteractionMass = null;
        myTempInteractionSpring = null;
    }

    /**
     *
     * @param p 
     */
    public void updateInteractionMassPosition (Point p) {
        myTempInteractionMass.setCenter(p.getX(), p.getY());
    }

    /**
     *
     * @param forceName 
     */
    public void toggleForce (String forceName) {
        for (Assembly a : myAssemblyList) {
            a.toggleForce(forceName);
        }
    }
}
